#include <linux/init.h>
#include <linux/kallsyms.h>
#include <linux/kernel.h>
#include <linux/list.h>
#include <linux/module.h>
#include <linux/proc_fs.h>
#include <linux/slab.h>
#include <linux/string.h>
#include <linux/string_helpers.h>
#include <linux/uaccess.h>

MODULE_LICENSE("GPL");
MODULE_AUTHOR("gxnong gxnong@linx-info.com");
MODULE_DESCRIPTION("test proc filesystem and list_head");
// ---------------------------------------- mode main
// ----------------------------------------------

struct app_ctl {
    char*            dsc;
    char*            mode;
    struct list_head list;
};

// init list
LIST_HEAD(app_ctl_list);

void add_node(struct app_ctl* ac) { list_add_tail(&(ac->list), &app_ctl_list); }

void print_list(void)
{
    struct app_ctl* ptr;
    printk("------------------------\n");
    list_for_each_entry(ptr, &app_ctl_list, list)
    {
        if (ptr) {
            printk("dsc:%s,mode:%s\n", ptr->dsc, ptr->mode);
        }
    }
}

void del_all_node(void)
{
    struct app_ctl *ptr, *next;
    list_for_each_entry_safe(ptr, next, &app_ctl_list, list)
    {
        printk("del dsc:%s,mode:%s", ptr->dsc, ptr->mode);
        if (ptr->dsc) {
            kfree(ptr->dsc);
            ptr->dsc = NULL;
        }

        list_del(&(ptr->list));
        if (ptr) kfree(ptr);
    }
}

// ------------------------------- module
// dsc------------------------------------

static struct proc_dir_entry* proc_test_dsc;

static char* dsc = NULL;
// write dsc callback
static ssize_t write_dsc(struct file* file, const char __user* ubuf,
                         size_t count, loff_t* ppos)
{
    if (count <= 0) return -EFAULT;

    // kfree memory by kmalloc before
    if (dsc != NULL) {
        kfree(dsc);
        dsc = NULL;
    }
    // maolloc new dsc memory
    dsc = (char*)kmalloc(count, GFP_KERNEL);
    if (dsc == NULL) {
        printk("kmalloc failure\n");
        return -EFAULT;
    }

    memset(dsc, 0, count);
    if (copy_from_user(dsc, ubuf, count)) return -EFAULT;

    printk(KERN_ALERT "set dsc %d char:%s\n", (int)count, dsc);
    return count;
}
// read dsc callback
static ssize_t read_dsc(struct file* file, char __user* ubuf, size_t count,
                        loff_t* ppos)
{
    size_t len;
    if (*ppos > 0 || dsc == NULL) return 0;
    len = sizeof(dsc);
    if (copy_to_user(ubuf, dsc, len)) return -EFAULT;
    printk("read dsc = %s\n", dsc);
    *ppos = *ppos + len;
    return len;
}

// set dsc callback
static struct file_operations fops_dsc = {
    .owner = THIS_MODULE,
    .read  = read_dsc,
    .write = write_dsc,
};

// ---------------------------------------- module mode
// -----------------------------------------------
static struct proc_dir_entry* proc_test_mode;
static char*                  mode   = NULL;
static char*                  mode_1 = "1";
static char*                  mode_0 = "0";

// mode write callback
static ssize_t write_mode(struct file* file, const char __user* ubuf,
                          size_t count, loff_t* ppos)
{
    struct app_ctl* node;
    char*           last_dsc;
    if (count <= 0) return -EFAULT;
    if (dsc == NULL) {
        printk(KERN_ALERT "dsc not set\n");
        return -EINVAL;
    }

    if (mode != NULL) {
        kfree(mode);
        mode = NULL;
    }
    // mollc new mode memory
    mode = kmalloc(count, GFP_KERNEL);
    memset(mode, 0, count);
    if (copy_from_user(mode, ubuf, count)) return -EFAULT;

    // create new list node
    node = kmalloc(sizeof(struct app_ctl), GFP_KERNEL);
    if (node == NULL) {
        printk(KERN_ALERT "malloc node failed\n");
        return 0;
    }
    if (strncmp(mode, "1", 1) == 0) {
        node->mode = mode_1;
    } else if (strncmp(mode, "0", 1) == 0) {
        node->mode = mode_0;
    } else {
        printk(KERN_ALERT "invalid mode\n");
    }
    // malloc new memory to save dsc in list
    last_dsc = kmalloc(strlen(dsc), GFP_KERNEL);
    strncpy(last_dsc, dsc, strlen(dsc));
    node->dsc = last_dsc;
    printk(KERN_ALERT "set mode %d char:%s\n", (int)count, node->mode);
    // add and print list
    add_node(node);
    print_list();
    return count;
}
// read mode callback
static ssize_t read_mode(struct file* file, char __user* ubuf, size_t count,
                         loff_t* ppos)
{
    size_t len;
    if (*ppos > 0 || mode == NULL) return 0;
    len = sizeof(mode);
    if (copy_to_user(ubuf, mode, len)) return -EFAULT;
    printk("read mode = %s\n", mode);
    *ppos = *ppos + len;
    return len;
}

// set mode callback
static struct file_operations fops_mode = {
    .owner = THIS_MODULE,
    .read  = read_mode,
    .write = write_mode,
};

// ------------------------------ module init and clean
// -------------------------------------

static int proc_init(void)
{
    proc_test_dsc  = proc_create("linx-dsc", 0664, NULL, &fops_dsc);
    proc_test_mode = proc_create("linx-mode", 0664, NULL, &fops_mode);
    if (proc_test_dsc == NULL || proc_test_mode == NULL) return -ENOMEM;
    printk(KERN_ALERT "proc_test init success!");
    return 0;
}

static void proc_cleanup(void)
{
    if (dsc) kfree(dsc);
    if (mode) kfree(mode);
    del_all_node();
    proc_remove(proc_test_dsc);
    proc_remove(proc_test_mode);
    printk(KERN_ALERT "proc_test cleanup");
}

module_init(proc_init);
module_exit(proc_cleanup);

/*
struct file_operations {
    struct module *owner;
    loff_t (*llseek) (struct file *, loff_t, int);
    ssize_t (*read) (struct file *, char __user *, size_t, loff_t *);
    ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *);
    ssize_t (*read_iter) (struct kiocb *, struct iov_iter *);
    ssize_t (*write_iter) (struct kiocb *, struct iov_iter *);
    int (*iterate) (struct file *, struct dir_context *);
    int (*iterate_shared) (struct file *, struct dir_context *);
    unsigned int (*poll) (struct file *, struct poll_table_struct *);
    long (*unlocked_ioctl) (struct file *, unsigned int, unsigned long);
    long (*compat_ioctl) (struct file *, unsigned int, unsigned long);
    int (*mmap) (struct file *, struct vm_area_struct *);
    int (*open) (struct inode *, struct file *);
    int (*flush) (struct file *, fl_owner_t id);
    int (*release) (struct inode *, struct file *);
    int (*fsync) (struct file *, loff_t, loff_t, int datasync);
    int (*fasync) (int, struct file *, int);
    int (*lock) (struct file *, int, struct file_lock *);
    ssize_t (*sendpage) (struct file *, struct page *, int, size_t, loff_t *,
int); unsigned long (*get_unmapped_area)(struct file *, unsigned long, unsigned
long, unsigned long, unsigned long); int (*check_flags)(int); int (*flock)
(struct file *, int, struct file_lock *); ssize_t (*splice_write)(struct
pipe_inode_info *, struct file *, loff_t *, size_t, unsigned int); ssize_t
(*splice_read)(struct file *, loff_t *, struct pipe_inode_info *, size_t,
unsigned int); int (*setlease)(struct file *, long, struct file_lock **, void
**); long (*fallocate)(struct file *file, int mode, loff_t offset, loff_t len);
    void (*show_fdinfo)(struct seq_file *m, struct file *f);
#ifndef CONFIG_MMU
    unsigned (*mmap_capabilities)(struct file *);
#endif
    ssize_t (*copy_file_range)(struct file *, loff_t, struct file *,
            loff_t, size_t, unsigned int);
    int (*clone_file_range)(struct file *, loff_t, struct file *, loff_t,
            u64);
    ssize_t (*dedupe_file_range)(struct file *, u64, u64, struct file *,
            u64);
};
*/
